package Chat;

import com.mysql.jdbc.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * The LTSChat business logic
 * @author valiadam
 */
public class ChatBusinessLogic
{

    public static final String AttrUsername = "username";
    public static final String AttrUserId = "userid";

    // <editor-fold desc="Singleton stuff">
    private ChatBusinessLogic()
    {
        try {
            //initiation stuff, read database etc..
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ltschat", "root", "bunica");
        } catch (SQLException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private static ChatBusinessLogic m_instance = null;

    //synchronized method -> avoid multithreading issues!
    public static synchronized ChatBusinessLogic Instance()
    {
        if (m_instance == null)
            m_instance = new ChatBusinessLogic();

        return m_instance;
    }
    // </editor-fold>

    // <editor-fold desc="SQL stuff">
    //these are reusable objects: to them must be synchronized!
    protected Connection connection = null;
    protected PreparedStatement m_addUserStatement = null;
    protected PreparedStatement m_findUserStatement = null;
    protected PreparedStatement m_verifyUserCredentialsStatement = null;
    protected PreparedStatement m_logUserInStatement = null;
    protected PreparedStatement m_logUserOutStatement = null;
    protected PreparedStatement m_getUserInfoStatement = null;
    protected PreparedStatement m_updateUserInfoStatement = null;
    protected PreparedStatement m_conversationStatement = null;
    protected PreparedStatement m_sendMessageStatement = null;
    protected PreparedStatement m_getUserNameStatement = null;

    protected PreparedStatement GetAddUserStatement() throws SQLException
    {
        if (m_addUserStatement == null)
        {
            //String [] columnNames = new String[1];
            //columnNames[0] = "id";
            m_addUserStatement = connection.prepareStatement("insert into users (name, password, description) values (?, ?, ?)", Statement.RETURN_GENERATED_KEYS);
        }

        return m_addUserStatement;
    }

    protected PreparedStatement GetFindUserStatement() throws SQLException
    {
        if (m_findUserStatement == null)
        {
            m_findUserStatement = connection.prepareStatement("select id, name, description from users where name = ?");
        }

        return m_findUserStatement;
    }

    protected PreparedStatement GetVerifyUserCredentialsStatement() throws SQLException
    {
        if (m_verifyUserCredentialsStatement == null)
        {
            m_verifyUserCredentialsStatement = connection.prepareStatement("select id, name, description from users where name = ? and password = ?");
        }

        return m_verifyUserCredentialsStatement;
    }

    protected PreparedStatement GetLogUserInStatement() throws SQLException
    {
        if (m_logUserInStatement == null)
        {
            m_logUserInStatement = connection.prepareStatement("insert into loggedin (userid, date) values (?, NOW())");
        }

        return m_logUserInStatement;
    }

    protected PreparedStatement GetLogUserOutStatement() throws SQLException
    {
        if (m_logUserOutStatement == null)
        {
            m_logUserOutStatement = connection.prepareStatement("delete from loggedin where userid = ?");
        }

        return m_logUserOutStatement;
    }

    protected PreparedStatement GetUserInfoStatement() throws SQLException
    {
        if (m_getUserInfoStatement == null)
        {
            m_getUserInfoStatement = connection.prepareStatement("select description from users where id=?");
        }

        return m_getUserInfoStatement;
    }
    
    protected PreparedStatement GetUpdateUserInfoStatement() throws SQLException
    {
        if (m_updateUserInfoStatement == null)
        {
            m_updateUserInfoStatement = connection.prepareStatement("update users set password=?, description=? where id=?");
        }

        return m_updateUserInfoStatement;
    }

    protected PreparedStatement GetConversationStatement() throws SQLException
    {
        if (m_conversationStatement == null)
        {
            m_conversationStatement = connection.prepareStatement("select * from messages where (senderid=? and receiverid=?) or (senderid=? and receiverid=?)");
        }

        return m_conversationStatement;
    }

    protected PreparedStatement GetSendMessageStatement() throws SQLException
    {
        if (m_sendMessageStatement == null)
        {
            m_sendMessageStatement = connection.prepareStatement("insert into messages (senderid, receiverid, tablename, message) values (?, ?, ?, ?)");
        }

        return m_sendMessageStatement;
    }

    protected PreparedStatement GetUserNameStatement() throws SQLException
    {
        if (m_getUserNameStatement == null)
        {
            m_getUserNameStatement = connection.prepareStatement("select name from users where id=?");

        }

        return m_getUserNameStatement;
    }

    // </editor-fold>

    // <editor-fold desc="Public methods - access to member variables must be synchronized!">

    /***
     * Adds a new user in the database and logs in by setting the session variables
     * @param username the new user name (must be unique or method fails)
     * @param pass
     * @param description
     * @param req the HttpServletRequest object
     * @return true on success, false on failure
     */
    public boolean AddNewUser(String username, String pass, String description, HttpServletRequest req)
    {
        try
        {
            int userId;

            //access to member variables: must use synchronization
            synchronized(this)
            {
                if (UsersListContains(username))
                    return false;

                userId = AddUserToDB(username, pass, description);
            
                if (userId >= 0)
                {
                    //also log user in
                    LogUserInDB(userId);
                    //add the username and id as session variables:
                    SessionAddUserInfo(req, username, userId);
                    return true;
                }
                else
                    return false;
            }
        } catch (SQLException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    public boolean VerifyUserCredentials(String username, String pass, HttpServletRequest req)
    {
        try 
        {
            ResultSet rs;

            //access to member variables: must use synchronization
            synchronized(this)
            {
                PreparedStatement verUsrCredStm = GetVerifyUserCredentialsStatement();
                verUsrCredStm.setString(1, username);
                verUsrCredStm.setString(2, pass);
                rs = verUsrCredStm.executeQuery();
            

                if (rs.next()) {
                    int userId = rs.getInt("id");
                    //log the user in the db:
                    LogUserInDB(userId);
                    //add the username as a session variable:
                    SessionAddUserInfo(req, username, userId);
                    return true;
                }
            }
            return false;
        } catch (SQLException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }
    // </editor-fold>

    ///Determines if a username exists
    private boolean UsersListContains(String user) throws SQLException
    {
        PreparedStatement findUsrStm = GetFindUserStatement();
        findUsrStm.setString(1, user);
        ResultSet rs = findUsrStm.executeQuery();
        if (rs.next())
            return true;
        return false;
    }

    ///Adds the logged in user's info as session variables
    private void SessionAddUserInfo(HttpServletRequest req, String username, int userId)
    {
        HttpSession session = req.getSession(true);
        session.setAttribute(AttrUsername, username);
        session.setAttribute(AttrUserId, userId);
    }

    ///Removes the logged in user's info from session
    public void LogOut(HttpServletRequest req)
    {
        HttpSession session = req.getSession();
        if (session == null)
            return;
        Integer intUsrId = (Integer)session.getAttribute(AttrUserId);
        if (intUsrId != null && intUsrId >0)
            try
            {
                synchronized(this)
                {
                    LogUserOutDB(intUsrId);
                }
            } catch (SQLException ex)
            {
                Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
            }
            
        session.removeAttribute(AttrUsername);
        session.removeAttribute(AttrUserId);
    }

    public String GetUserInfo(int userID)
    {
        ResultSet rs;
        try {
            synchronized (this)
            {
                PreparedStatement usrInfStmt = GetUserInfoStatement();
                usrInfStmt.setInt(1, userID);
                rs = usrInfStmt.executeQuery();
                if (rs.next())
                {
                    return rs.getString("description");
                }
            }
        } catch (SQLException ex)
        {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        return null;
    }

    public boolean UpdateUserInfo(int userID, String newPass, String newDesc)
    {
        boolean bSuccess = false;
        try {
            synchronized(this)
            {
                PreparedStatement updateUsrInfStmt = GetUpdateUserInfoStatement();
                updateUsrInfStmt.setString(1, newPass);
                updateUsrInfStmt.setString(2, newDesc);
                updateUsrInfStmt.setInt(3, userID);
                bSuccess = (updateUsrInfStmt.executeUpdate() > 0);
            }
        } catch (SQLException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
        }
        return bSuccess;
    }

    public String GetConversation(int currentUserId, int otherUserId)
    {
        String strConversation = "";
        try {
            synchronized (this)
            {                
                //get the two guy's names:
                String strCurrUsrName = null;
                String strOthUsrName = null;
                PreparedStatement getUsrNameStmt = GetUserNameStatement();
                getUsrNameStmt.setInt(1, currentUserId);
                ResultSet rs = getUsrNameStmt.executeQuery();
                if (rs.next())
                {
                    strCurrUsrName = rs.getString("name");
                }

                getUsrNameStmt.setInt(1, otherUserId);
                rs = getUsrNameStmt.executeQuery();
                if (rs.next())
                {
                    strOthUsrName = rs.getString("name");
                }

                if (strCurrUsrName != null && strOthUsrName != null)
                {
                    PreparedStatement convStmt = GetConversationStatement();
                    convStmt.setInt(1, currentUserId);
                    convStmt.setInt(2, otherUserId);
                    convStmt.setInt(3, otherUserId);
                    convStmt.setInt(4, currentUserId);
                    rs = convStmt.executeQuery();
                    while (rs.next())
                    {
                        String strMessage = rs.getString("message");
                        int senderId = rs.getInt("senderid");
                        //add the sender's name
                        strConversation += "<div class='un_mesaj'><div class='username'>" + (senderId == currentUserId ? strCurrUsrName : strOthUsrName);
                        //add the message:
                        //USERNAMEMESAJ
                        strConversation += "</div><div class='message'>: " + strMessage + "</div></div>";
                    }
                }
            }

        } catch (SQLException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
        }

        return strConversation;
    }

    public void SendMessage (int currUsr, int otherUsr, String message)
    {
        try {
            synchronized(this){
            PreparedStatement sendMsgStmt = GetSendMessageStatement();
            sendMsgStmt.setInt(1, currUsr);
            sendMsgStmt.setInt(2, otherUsr);
            sendMsgStmt.setString(3, "users");
            sendMsgStmt.setString(4, message);
            sendMsgStmt.executeUpdate();}
        } catch (SQLException ex) {
            Logger.getLogger(ChatBusinessLogic.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Adds a new user to the database
     * @param username
     * @param pass
     * @param description
     * @return the newly added user's generater id on success, -1 on failure
     * @throws SQLException
     */
    private int AddUserToDB(String username, String pass, String description) throws SQLException
    {
        PreparedStatement addUsrStm = GetAddUserStatement();
        addUsrStm.setString(1, username);
        addUsrStm.setString(2, pass);
        addUsrStm.setString(3, description);

        addUsrStm.executeUpdate();
        ResultSet rs = addUsrStm.getGeneratedKeys();
        if (rs.next())
            return rs.getInt("GENERATED_KEY");
        else
            return -1;
    }

    private void LogUserInDB(int userID) throws SQLException
    {
        PreparedStatement logUserInStm = GetLogUserInStatement();
        logUserInStm.setInt(1, userID);

        logUserInStm.executeUpdate();
    }

    private boolean LogUserOutDB(int userID) throws SQLException
    {
        PreparedStatement logUserOutStm = GetLogUserOutStatement();
        logUserOutStm.setInt(1, userID);

        logUserOutStm.executeUpdate();
        int resCount = logUserOutStm.getUpdateCount();
        return resCount > 0;
    }
}
